<?php defined('SYSPATH') or die('No direct script access.');
/**
 * Provides http server communication options using [Stream]
 * Mainly used to communiate with http
 * Add Support HTTP/1.1 by Default
 * Only Support Connection Close Mode
 *
 * @author     anthony Chen
 */
class Http {

	public static $allow_self_signed = true;
	public static $non_blocking = false;
	/**
	 * @var  array  default header options
	 */
	public static $default_options = array (
			'User-Agent'=> 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit /538.36 (KHTML, like Gecko) Pexcel/6.998',
			'Connection'=> 'Close' //Need to close the request every time for HTTP 1.1
		);

	/**
	 * return Array lines of headers
	 * Overwrite by $options
	 *
	 * @param Array $options, key value pairs of Header options
	 * 
	 * @return Array,Array line of headers
	 */
	private static function getHeaders($options){
		if ($options === NULL) {
			// Use default options
			$options = self::$default_options;
		} else {
			//Merge the $options with $default_options, if the value set in $options,
			//Value in $default_options will be overwrite
			$options =$options + self::$default_options ;
		}

		$headers = array();
		foreach($options as $k=>$v){
			$headers[] = $k.': '.$v;
		}

		return $headers;
	}

	/**
	 * Returns the output of a http URL. 
	 * may be used.
	 *
	 * @param   string   http base URL or FULL url
	 * @param   array    Header options
	 * @param   array $data Get Param
	 * @param   array &$rr_headers,Request Response headers
	 * 		    if Assigned,the response header will be populated
	 * 			If ['fingerprint'] is set, then finger is checked in SSL Context
	 * 			If ['ignore_errors'] is set, then ignore http error code other than 200
	 *
	 * @return  string, Raw String of Http body
	 */
	public static function get($url, array $options = NULL,$data = NULL,&$rr_headers = NULL) {
		$headers = self::getHeaders($options);
		$params = ['http' => [
			'method' => 'GET',
			//Defautl HTTP 1.1 and with Connection Close
			'protocol_version'=>'1.1',
			//'Connection'=> 'Close' //Need to close the request every time for HTTP 1.1
		]];

		if(Arr::get($rr_headers,'ignore_errors') == true){
			$params['http']['ignore_errors'] = true;
		}

		$params['http']['header'] = $headers;

		if($data){
			$url .= '?'.http_build_query($data); 
		}
		$ctx = stream_context_create($params);
		if(self::$allow_self_signed == true){
			stream_context_set_option($ctx,["ssl"=>["allow_self_signed"=>true,"verify_peer_name"=>false,"verify_peer"=>false]]);
			$_finger = Arr::get($rr_headers,'fingerprint');
			if($_finger){
				stream_context_set_option($ctx,["ssl"=>["peer_fingerprint"=>$_finger]]);
			}
		}

		$fp = fopen($url, 'rb', false, $ctx);
		if (!$fp) {
			throw new Exception("Connection failed: $url");
		}

		if(self::$non_blocking == TRUE){
			stream_set_blocking($fp,0);
		}

		if($rr_headers !== NULL){
			$rr_headers = stream_get_meta_data($fp);
		}

		$response = stream_get_contents($fp);
		if ($response === false) {
			throw new Exception("Reading data Failed: $url");
		}
		fclose($fp);
		return $response;
	}

	/**
	 * Post with request options and data 
	 *
	 * @param String url, FULL url
	 * @param Array $options , key=>value pairs array
	 * @param Array $data ,Post Data pairs
	 * @param   array &$rr_headers,Request Response headers
	 * 		    if Assigned,the response header will be populated
	 * 			If ['fingerprint'] is set, then finger is checked in SSL Context
	 * 			If ['ignore_errors'] is set, then ignore http error code other than 200
	 * @return  string, Raw String of Http body
	 */
	public static function post($url,  $options = null,$data=NULL,&$rr_headers = NULL) {
		//Restricted the Form formate
		if(is_array($data)){
			$data = http_build_query($data);
		}

		$options['Content-type'] = Arr::get($options,'Content-type','application/x-www-form-urlencoded');
		$options['Content-Length'] =Arr::get($options,'Content-Length',strlen($data));

		$params = ['http' => [
			'method' => 'POST',
			'protocol_version'=>'1.1',
			//'Connection'=> 'Close', //Need to close the request every time for HTTP 1.1
			//'ignore_errors'=>true,
			'content' => $data
		]];

		if(Arr::get($rr_headers,'ignore_errors') == true){
			$params['http']['ignore_errors'] = true;
		}

		$headers = self::getHeaders($options);
		$params['http']['header'] = $headers;

		$ctx = stream_context_create($params);
		if(self::$allow_self_signed == true){
			stream_context_set_option($ctx,["ssl"=>["allow_self_signed"=>true,"verify_peer_name"=>false,"verify_peer"=>false]]);
			$_finger = Arr::get($rr_headers,'fingerprint');
			if($_finger){
				stream_context_set_option($ctx,["ssl"=>["peer_fingerprint"=>$_finger]]);
			}
		}

		$fp = fopen($url, 'rb', false, $ctx);
		if (!$fp) {
			throw new Exception("Connection Failed: $url ");
		}

		if(self::$non_blocking == TRUE){
			stream_set_blocking($fp,0);
		}

		if($rr_headers !== NULL){
			$rr_headers = stream_get_meta_data($fp);
		}

		$response = stream_get_contents($fp);
		if ($response === false) {
			throw new Exception("Reading data failed: $url");
		}
		fclose($fp);
		return $response;
	} 

	/**
	 * Inflate zipped content
	 *
	 * @param String $_content, gzipped content
	 *
	 * @return String, Inflated content
	 */
	public static function inflate($_content){
		//deflate add 10 charaters before inflate format and 8 charaters checksum append
		//gzdecode is not availible for ALL PHP even gzencode is avalible
		$_content = substr($_content, 10,-8); 
		return gzinflate($_content);

	}

	/**
	 * Check if the reponse content is zipped from response header
	 *
	 * @param Array $_response_header, Response header captured from get/post
	 *
	 * @return Boolean, True for zipped contented
	 */
	public static function isZipped($_response_header){
		if (preg_grep('/^Content-Encoding:\s*gzip/i',$_response_header['wrapper_data'])){
			return TRUE;
		}else{
			return False;
		}
	}

	/**
	 * Parse response headers into K V data
	 *
	 * @param Array $headers, wrapper data return from http request
	 *
	 * @return Array , the parsed headers of http
	 */
	public static function parseHeaders( $_response_header ) {
		$headers = Arr::get($_response_header,'wrapper_data',[]);
		$head = array();
		foreach( $headers as $k=>$v )
		{
			$t = explode( ':', $v, 2 );
			if( isset( $t[1] ) )
				$head[ trim($t[0]) ] = trim( $t[1] );
			else
			{
				$head[] = $v;
				if( preg_match( "#HTTP/[0-9\.]+\s+([0-9]+)#",$v, $out ) )
					$head['reponse_code'] = intval($out[1]);
			}
		}
		return $head;
	}
} // End http

